package main

// https://xiaoxuez.github.io/2020/07/27/binance-tss/
// https://aping-dev.com/index.php/archives/519/

import (
	"crypto/ecdsa"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"math/big"
	"math/rand"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"sync/atomic"

	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/crypto"

	"bsinpass/go/mpc/tss-lib/common"
	"bsinpass/go/mpc/tss-lib/ecdsa/keygen"
	"bsinpass/go/mpc/tss-lib/ecdsa/signing"
	"bsinpass/go/mpc/tss-lib/test"
	"bsinpass/go/mpc/tss-lib/tss"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
)

const (
	// To change these parameters, you must first delete the text fixture files in test/_fixtures/ and then run the keygen test alone.
	// Then the signing and resharing tests will work with the new n, t configuration using the newly written fixture files.
	TestParticipants = 4
	TestThreshold    = TestParticipants / 2
)
const (
	testFixtureDirFormat  = "%s/_fixtures"
	testFixtureFileFormat = "keygen_data_%d.json"
)

// 创建文件目录
func makeTestFixtureFilePath(partyIndex int) string {
	_, callerFileName, _, _ := runtime.Caller(0)
	srcDirName := filepath.Dir(callerFileName)
	fixtureDirName := fmt.Sprintf(testFixtureDirFormat, srcDirName)
	return fmt.Sprintf("%s/"+testFixtureFileFormat, fixtureDirName, partyIndex)
}

// 保存分片数据
func tryWriteTestFixtureFile(index int, data keygen.LocalPartySaveData) {
	// 创建文件目录
	fixtureFileName := makeTestFixtureFilePath(index)
	// fixtureFileName := "example.txt"
	// fixture file does not already exist?
	// if it does, we won't re-create it here
	fi, err := os.Stat(fixtureFileName)
	if !(err == nil && fi != nil && !fi.IsDir()) {
		fd, err := os.OpenFile(fixtureFileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
		if err != nil {
			fmt.Errorf("unable to open fixture file %s for writing", fixtureFileName)
		}
		bz, err := json.Marshal(&data)
		if err != nil {
			fmt.Errorf("unable to marshal save data for fixture file %s", fixtureFileName)
		}
		// 将分片数据写入文件
		_, err = fd.Write(bz)
		if err != nil {
			fmt.Errorf("unable to write to fixture file %s", fixtureFileName)
		}
		fmt.Errorf("Saved a test fixture file for party %d: %s", index, fixtureFileName)
	} else {
		fmt.Errorf("Fixture file already exists for party %d; not re-creating: %s", index, fixtureFileName)
	}
	//
}

// 1、Setup
func LoadKeygenTestFixtures(qty int, optionalStart ...int) ([]keygen.LocalPartySaveData, tss.SortedPartyIDs, error) {
	keys := make([]keygen.LocalPartySaveData, 0, qty)
	start := 0
	if 0 < len(optionalStart) {
		start = optionalStart[0]
	}
	for i := start; i < qty; i++ {
		fixtureFilePath := makeTestFixtureFilePath(i)
		bz, err := os.ReadFile(fixtureFilePath)
		if err != nil {
			return nil, nil, errors.Wrapf(err,
				"could not open the test fixture for party %d in the expected location: %s. run keygen tests first.",
				i, fixtureFilePath)
		}
		var key keygen.LocalPartySaveData
		if err = json.Unmarshal(bz, &key); err != nil {
			return nil, nil, errors.Wrapf(err,
				"could not unmarshal fixture data for party %d located at: %s",
				i, fixtureFilePath)
		}
		for _, kbxj := range key.BigXj {
			kbxj.SetCurve(tss.S256())
		}
		// 阈值密钥生成
		key.ECDSAPub.SetCurve(tss.S256())
		keys = append(keys, key)
	}
	partyIDs := make(tss.UnSortedPartyIDs, len(keys))
	for i, key := range keys {
		pMoniker := fmt.Sprintf("%d", i+start+1)
		// 参与节点的公钥（key.ShareID）
		partyIDs[i] = tss.NewPartyID(pMoniker, pMoniker, key.ShareID)
	}
	// 获取有序的PartyID：每个参与节点都是有序的
	sortedPIDs := tss.SortPartyIDs(partyIDs)
	return keys, sortedPIDs, nil
}

// 随机读取几个密钥分片
func LoadKeygenTestFixturesRandomSet(qty, fixtureCount int) ([]keygen.LocalPartySaveData, tss.SortedPartyIDs, error) {
	keys := make([]keygen.LocalPartySaveData, 0, qty)
	plucked := make(map[int]interface{}, qty)
	for i := 0; len(plucked) < qty; i = (i + 1) % fixtureCount {
		_, have := plucked[i]
		if pluck := rand.Float32() < 0.5; !have && pluck {
			plucked[i] = new(struct{})
		}
	}
	for i := range plucked {
		fixtureFilePath := makeTestFixtureFilePath(i)
		bz, err := os.ReadFile(fixtureFilePath)
		if err != nil {
			return nil, nil, errors.Wrapf(err,
				"could not open the test fixture for party %d in the expected location: %s. run keygen tests first.",
				i, fixtureFilePath)
		}
		var key keygen.LocalPartySaveData
		if err = json.Unmarshal(bz, &key); err != nil {
			return nil, nil, errors.Wrapf(err,
				"could not unmarshal fixture data for party %d located at: %s",
				i, fixtureFilePath)
		}
		for _, kbxj := range key.BigXj {
			kbxj.SetCurve(tss.S256())
		}
		// use ECDSA
		key.ECDSAPub.SetCurve(tss.S256())
		// 公私钥对
		keys = append(keys, key)
	}
	partyIDs := make(tss.UnSortedPartyIDs, len(keys))
	j := 0
	for i := range plucked {
		key := keys[j]
		pMoniker := fmt.Sprintf("%d", i+1)
		partyIDs[j] = tss.NewPartyID(pMoniker, pMoniker, key.ShareID)
		j++
	}
	sortedPIDs := tss.SortPartyIDs(partyIDs)
	sort.Slice(keys, func(i, j int) bool { return keys[i].ShareID.Cmp(keys[j].ShareID) == -1 })
	return keys, sortedPIDs, nil
}

// 2、Keygen
func testDistibutedKeyGeneration() {
	fmt.Println("testDistibutedKeyGeneration")
	testThreshold := TestThreshold
	testParticipants := TestParticipants

	// 从文件中加载 pIDs
	fixtures, pIDs, err := LoadKeygenTestFixtures(testParticipants)
	if err != nil {
		common.Logger.Info("No test fixtures were found, so the safe primes will be generated from scratch. This may take a while...")
		// 根据参与者数量testParticipants 创建参与者party的id
		pIDs = tss.GenerateTestPartyIDs(testParticipants)
	}
	// 用于P2P（点对点）通信的上下文p2pCtx
	p2pCtx := tss.NewPeerContext(pIDs)
	parties := make([]*keygen.LocalParty, 0, len(pIDs))
	// 创建了几个通道（channel），用于处理错误（errCh）、传输消息（outCh）和接收签名完成信号（endCh）
	errCh := make(chan *tss.Error, len(pIDs))
	outCh := make(chan tss.Message, len(pIDs))
	endCh := make(chan keygen.LocalPartySaveData, len(pIDs))

	//接收消息的Update
	updater := test.SharedPartyUpdater

	startGR := runtime.NumGoroutine()

	// init the parties
	for i := 0; i < len(pIDs); i++ {
		var P *keygen.LocalParty
		// 构建生成分片参数
		params := tss.NewParameters(tss.S256(), p2pCtx, pIDs[i], len(pIDs), testThreshold)
		if i < len(fixtures) {
			// 生成分片和保存数据 params还包括m参与者n阈值的参数
			P = keygen.NewLocalParty(params, outCh, endCh, fixtures[i].LocalPreParams).(*keygen.LocalParty)
		} else {
			P = keygen.NewLocalParty(params, outCh, endCh).(*keygen.LocalParty)
		}
		parties = append(parties, P)
		// // 创建一个Goroutine 根据参与者数量pIDs，异步执行线程生成密钥分片数据
		go func(P *keygen.LocalParty) {
			if err := P.Start(); err != nil {
				errCh <- err
			}
		}(P)
	}

	// 是一个标记: keygen
	var ended int32
	// 捕获生成分片私钥结果
keygen:
	for {
		// 活跃线程
		fmt.Printf("ACTIVE GOROUTINES: %d\n", runtime.NumGoroutine())
		// 线程监听
		select {
		case err := <-errCh:
			common.Logger.Errorf("Error: %s", err)
			break keygen

		case msg := <-outCh:
			// 有消息发出，这里消息有两种，一种是广播，需将消息传递给出自己之外的参与者；一种是定向，需将消息传递给定向的那个参与者
			dest := msg.GetTo()
			//如果to == nill,就说明是广播消息
			if dest == nil { // broadcast!
				for _, P := range parties {
					if P.PartyID().Index == msg.GetFrom().Index {
						continue
					}
					//将消息传递给对应party
					fmt.Printf("将消息传递给对应party")
					go updater(P, msg, errCh)
				}
			} else { // point-to-point!
				if dest[0].Index == msg.GetFrom().Index {
					fmt.Errorf("party %d tried to send a message to itself (%d)", dest[0].Index, msg.GetFrom().Index)
					return
				}
				//定向将消息传递给对应的party
				fmt.Printf("定向将消息传递给对应的party")
				go updater(parties[dest[0].Index], msg, errCh)
			}

		case save := <-endCh:
			// fmt.Printf("endCh： save: %v", save)
			// SAVE a test fixture file for this P (if it doesn't already exist)
			// .. here comes a workaround to recover this party's index (it was removed from save data)
			// 分布式保存分片数据：根据序号保存分片密钥
			index, _ := save.OriginalIndex()
			// 去分发密钥分片
			tryWriteTestFixtureFile(index, save)
			// 原子操作（atomic）
			atomic.AddInt32(&ended, 1)
			// 接收到参与者数量的数据后则结束循环
			if atomic.LoadInt32(&ended) == int32(len(pIDs)) {
				fmt.Printf("Done. Received save data from %d participants", ended)
				fmt.Println("")
				fmt.Printf("Start goroutines: %d, End goroutines: %d", startGR, runtime.NumGoroutine())
				break keygen
			}
		}
	}

}

// 3、Signing
func testDistibutedSigning() {
	const RecoveryIDOffset = 64
	fmt.Println("testDistibutedSigning")
	testThreshold := TestThreshold
	testParticipants := TestParticipants
	// 要签名的数据
	// num, err := strconv.Atoi("0xf56a3326047b8d2c2353e7e9fc3ce05bcfdd7aff4f4f05a650c00750d29e4692")
	// messageToSign := big.NewInt(int64(num))

	data := []byte("0x2312724e8af085c7adde2130b43c1cfe5fec8a5675badf42bddbad1b209c5134")
	messageToSign := crypto.Keccak256Hash(data).Big()

	// 随机加载几个密钥分片
	keys, signPIDs, err := LoadKeygenTestFixturesRandomSet(testThreshold+1, testParticipants)
	if err != nil {
		common.Logger.Error("should load keygen fixtures")
	}

	// PHASE: signing
	// use a shuffled selection of the list of parties for this test
	// init the parties
	// 初始化了一个用于P2P（点对点）通信的上下文p2pCtx
	p2pCtx := tss.NewPeerContext(signPIDs)
	parties := make([]*signing.LocalParty, 0, len(signPIDs))

	// 创建了几个通道（channel），用于处理错误（errCh）、传输消息（outCh）和接收签名完成信号（endCh）
	errCh := make(chan *tss.Error, len(signPIDs))
	outCh := make(chan tss.Message, len(signPIDs))
	// 多签结束通道
	endCh := make(chan common.SignatureData, len(signPIDs))
	//接收消息的Update
	updater := test.SharedPartyUpdater

	// init the parties
	//启动testThreshold+1参与者对应的分片密钥签名
	for i := 0; i < len(signPIDs); i++ {
		params := tss.NewParameters(tss.S256(), p2pCtx, signPIDs[i], len(signPIDs), testThreshold)
		// 每个分片对签名数据进行处理 messageToSign：要签名数据
		// fmt.Println("用来签名的key", keys[i])
		P := signing.NewLocalParty(messageToSign, params, keys[i], outCh, endCh).(*signing.LocalParty)
		// 将参与者的party存储，后续接收到信息时，需传递给对应的party
		parties = append(parties, P)
		// 创建一个Goroutine执行签名
		go func(P *signing.LocalParty) {
			if err := P.Start(); err != nil {
				errCh <- err
			}
		}(P)
	}
	var ended int32

	// 跳转接受语句
signing:
	// 无限循环
	for {
		// fmt.Printf("ACTIVE GOROUTINES: %d\n", runtime.NumGoroutine())
		// 使用select语句来监听不同通道上的消息 监听多个通道上的事件，并当任一事件发生时执行相应的处理代码
		select {
		// 对通道数据进行接受操作
		case err := <-errCh:
			common.Logger.Errorf("Error: %s", err)
			assert.FailNow(nil, err.Error())
			break signing

		case msg := <-outCh:
			//有消息发出，这里消息有两种，一种是广播，需将消息传递给出自己之外的参与者；一种是定向，需将消息传递给定向的那个参与者
			dest := msg.GetTo()
			//如果to == nill,就说明是广播消息
			if dest == nil {
				for _, P := range parties {
					if P.PartyID().Index == msg.GetFrom().Index {
						continue
					}
					//将消息传递给对应party
					go updater(P, msg, errCh)
				}
			} else {
				if dest[0].Index == msg.GetFrom().Index {
					common.Logger.Fatalf("party %d tried to send a message to itself (%d)", dest[0].Index, msg.GetFrom().Index)
				}
				//定向将消息传递给对应的party
				go updater(parties[dest[0].Index], msg, errCh)
			}

		case sig := <-endCh:

			//到这里签名有结果了，多签就完成了，签名的数据分别是R、S
			fmt.Println("R byte=>", sig.R)
			fmt.Println("R=>", new(big.Int).SetBytes(sig.R))
			fmt.Println("S byte=>", sig.S)
			fmt.Println("S=>", new(big.Int).SetBytes(sig.S))
			fmt.Println("sig=>", sig.Signature)
			fmt.Println("sig length:", len(sig.Signature))
			fmt.Println("sig hex=>", hex.EncodeToString(sig.Signature))

			// Convert to Ethereum signature format with 'recovery id' v at the end.
			// 补齐V值
			// https://blog.csdn.net/weixin_39842528/article/details/124358506
			// 检查大数的最低位为0是偶数
			if new(big.Int).SetBytes(sig.Signature[:32]).Bit(0) == 0 {
				sig.Signature = append(sig.Signature, 27)
				fmt.Println("V 值:27")
			} else {
				sig.Signature = append(sig.Signature, 28)
				fmt.Println("V 值:28")
			}

			fmt.Println("补齐v 值后sig length:", len(sig.Signature))

			// 复制从1到最后一个元素
			// copy(sig.Signature, sig.Signature[1:])
			// sig.Signature[RecoveryIDOffset] = V

			if len(sig.Signature) != 65 {
				panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig.Signature)))
			}

			fmt.Println("以太坊签名结果 :", sig.Signature)
			fmt.Println("发送交易签名 :", hexutil.Encode(sig.Signature))

			r := new(big.Int).SetBytes(sig.Signature[:32])
			s := new(big.Int).SetBytes(sig.Signature[32:64])
			v := new(big.Int).SetBytes([]byte{sig.Signature[64]})

			fmt.Printf("r: %s\n", r)
			fmt.Printf("s: %s\n", s)
			fmt.Printf("v: %s\n", v)

			// 接收到足够数量的完成信号（通过endCh）后，程序会汇总签名结果，并进行验证
			//到这里签名有结果了，多签就完成了，签名的数据分别是R、S
			//因为是本地启动了多个协程作为参与者，且是使用的相同的channel，所以endCh会收到每一个参与者的多签结果
			atomic.AddInt32(&ended, 1) // 原子操作（atomic）
			//当所以的参与者都完成了签名
			if atomic.LoadInt32(&ended) == int32(len(signPIDs)) {
				common.Logger.Debug("Done. Received signature data from %d participants", ended)
				R := parties[0].Temp.BigR
				r := parties[0].Temp.Rx
				// 签名结果
				fmt.Println("mpc钱包签名结果：")
				fmt.Printf("sign result: R(%s, %s), r=%s\n", R.X().String(), R.Y().String(), r.String())

				fmt.Printf("sign r: %s\n", r.String())

				modN := common.ModInt(tss.S256().Params().N)

				// BEGIN check s correctness
				sumS := big.NewInt(0)
				for _, p := range parties {
					sumS = modN.Add(sumS, p.Temp.Si)
				}

				//这里计算出来的sumS和sig.S是相同的值，
				fmt.Printf("S: %s\n", sumS.String())
				fmt.Println("pub", keys[0].ECDSAPub.X(), keys[0].ECDSAPub.Y())

				// s是
				fmt.Printf("sign s: %s\n", sumS.String())
				// END check s correctness

				// BEGIN ECDSA verify
				pkX, pkY := keys[0].ECDSAPub.X(), keys[0].ECDSAPub.Y()
				pk := ecdsa.PublicKey{
					Curve: tss.EC(),
					X:     pkX,
					Y:     pkY,
				}
				// 将公钥转出成以太坊地址
				fmt.Println(pk)
				address := crypto.PubkeyToAddress(pk).Hex()
				// 0x2e9e18937879adAA27fccD3056Dc7C687a564e23
				fmt.Println("mpc钱包地址：")
				fmt.Println(address)

				// 签名验证
				// 去掉最后一位
				fmt.Println("以太坊验证签名参数 :", sig.Signature)
				fmt.Println("以太坊验证签名参数 :", hexutil.Encode(sig.Signature))
				modified := sig.Signature[:len(sig.Signature)-1]
				publicKeyBytes := crypto.FromECDSAPub(&pk)
				fmt.Println("以太坊验证签名参数 :", modified)
				fmt.Println("以太坊验证签名参数 :", len(modified))
				okSI := crypto.VerifySignature(publicKeyBytes, messageToSign.Bytes(), modified)
				fmt.Println("verify pass?", okSI)

				// 验证签名
				ok := ecdsa.Verify(&pk, messageToSign.Bytes(), R.X(), sumS)
				assert.True(nil, ok, "ecdsa verify must pass")
				fmt.Println("ECDSA signing test done.")
				// END ECDSA verify
				break signing
			}
		}
	}

}

func generate() (string, string) {
	fmt.Println("generate:")
	privateKey, err := crypto.GenerateKey()
	if err != nil {
		log.Fatal(err)
	}
	privateKeyBytes := crypto.FromECDSA(privateKey)
	privateKeyString := hexutil.Encode(privateKeyBytes)
	publicKey := privateKey.Public()
	publicKeyECDSA, _ := publicKey.(*ecdsa.PublicKey)
	address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
	fmt.Println(address)
	fmt.Println(privateKeyString)
	return privateKeyString, address

}

func main() {
	// 生成分片密钥
	testDistibutedKeyGeneration()
	// 分片签名
	testDistibutedSigning()
	// 根据公钥生成地址
	// generate()
	// testSign()
}

func testSign() {
	privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
	if err != nil {
		log.Fatal(err)
	}

	data := []byte("0xf56a3326047b8d2c2353e7e9fc3ce05bcfdd7aff4f4f05a650c00750d29e4692")
	hash := crypto.Keccak256Hash(data)
	fmt.Printf("签名hash: %s\n", hash.Hex())
	fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8
	signature, err := crypto.Sign(hash.Bytes(), privateKey)

	fmt.Println("签名", signature)
	fmt.Printf("签名长度 %d \n", len(signature))

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301
}
